package com.liruo.enumconverter.core.converter;

import com.liruo.enumconverter.model.SourceEnum;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @Author:liruo
 * @Date:2023-02-22-17:48:10
 * @Desc
 */
public class SourceEnumGenericConverter implements ConditionalGenericConverter {
    private final Map<String, SourceEnum<?>> cache = new ConcurrentReferenceHashMap<>(16);
    public boolean isSourceEnum(Class<?> sourceEnum){
        if(!SourceEnum.class.isAssignableFrom(sourceEnum) || !sourceEnum.isEnum()){
            return false;
        }
        Class<?> sourceClass = ((SourceEnum<?>)sourceEnum.getEnumConstants()[0]).source().getClass();
        return Integer.class == sourceClass || String.class == sourceClass;
    }
    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        Class<?> sourceClazz = sourceType.getType();
        Class<?> targetClazz = targetType.getType();
        if((sourceClazz == String.class || sourceClazz == Integer.class) &&
                (SourceEnum.class.isAssignableFrom(targetClazz) && targetClazz != Enum.class && targetClazz.isEnum())){
            //deserialize
            return true;
        }else if((targetClazz == String.class || targetClazz == Integer.class) &&
                (SourceEnum.class.isAssignableFrom(sourceClazz) && sourceClazz != Enum.class && sourceClazz.isEnum())){
            //serialize
            return true;
        }
        return false;
    }

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        HashSet<ConvertiblePair> set = new HashSet<>();
        set.add(new ConvertiblePair(Integer.class, SourceEnum.class));
        set.add(new ConvertiblePair(String.class, SourceEnum.class));
        set.add(new ConvertiblePair(SourceEnum.class, Integer.class));
        set.add(new ConvertiblePair(SourceEnum.class, String.class));
        return set;
    }

    /**
     *
     * @param source deserialize:要求是Integer(或者是字符串的Integer)或者String
     * @param sourceType 是SourceEnum就serialize
     * @param targetType 是SourceEnum就deserialize
     * @return
     */
    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        if(SourceEnum.class.isAssignableFrom(sourceType.getType())){
            return serialize((SourceEnum<?>)source);
        }else if(SourceEnum.class.isAssignableFrom(targetType.getType())){
            Class<? extends SourceEnum<?>> targetClass = (Class<? extends SourceEnum<?>>)targetType.getType();
            return deserialize(source, targetClass);
        }
        return null;
    }

    /**
     *
     * @param source 要求是Integer(或者是字符串的Integer)或者String
     * @param enumClass  获取source类型，以及获取所有enumConstant进行遍历匹配
     * @return
     */
    public <T extends SourceEnum<?>> T deserialize(Object source, Class<T> enumClass) {
        boolean isIntSource = ((SourceEnum<?>) enumClass.getEnumConstants()[0])
                .source().getClass()
                .isAssignableFrom(Integer.class);
        if(isIntSource && source.getClass() == String.class){
            try {
                source = Integer.parseInt((String) source);
            } catch (NumberFormatException e) {
                throw new RuntimeException(
                        "deserialize SourceEnum fail require sourceClass="
                                + Integer.class.getName()
                        +", and if sourceClass=String require is a valid Integer String"
                        + ", but give=" + source,
                        e);
            }
        }
        String cacheKey = enumClass.getName() + "=" + source;
        SourceEnum<?> cacheSourceEnum = this.cache.get(cacheKey);
        if(cacheSourceEnum != null){
            return (T)cacheSourceEnum;
        }
        for (T enumConstant : enumClass.getEnumConstants()) {
            if(enumConstant.source().equals(source)){
                this.cache.put(cacheKey, enumConstant);
                return enumConstant;
            }
        }
        return null;
    }

    private Object serialize(SourceEnum<?> source) {
        return source.source();
    }
}
